Módulo Audioprocessing para el procesamiento de Audio

La demostración del módulo consta de 6 partes:

  1. Reproducción lenta del audio, el usuario deberá definir qué tan lenta quiere la reproducción.
  2. Reproducción rápida del audio, el usuario deberá definir qué tan rápida quiere la reproducción.
  3. Reproducción hacia atrás del audio
  4. Limpiar el audio de ruido de alta frecuencia.
  5. Ecualización de las frecuencias bajas y altas del audio.
  6. Mezclar dos audios en el mismo archivo.

Módulo audioprocessing.py

El notebook fue diseñado bajo los parámetros de diseño modular, pensando en un código limpio, entendible y fácil de reutilizar.

In [ ]:
from scipy.io import wavfile
import numpy as np
import IPython.display as ipd
from scipy.fftpack import *
import matplotlib.pyplot as plt
import math

def playAudio(file):
    """
    Muestra en pantalla el reproductor de iPython Display para un archivo de
    formato .wav.

    Parámetros
    ----------
    file: string
        Nombre del archivo en formato .wav que contiene audio en formato
        mono o estéreo.
    """
    return ipd.Audio(file)

def ReadAudio(file):
    """
    Retorna la tasa de muestras por minuto y la matriz con los datos del audio}
    en formato mono o estéreo.

    Parámetros
    ----------
    file: string
        Nombre del archivo en formato .wav que contiene audio en formato
        mono o estéreo.
        
    Retorna
    --------
    List: 
        [rate,data]
    rate: int 
        Muestras por segundo
    data: numpy ndarray 
        Matriz con audio en mono o estéreo
    """
    rate,data=wavfile.read(file)
    return [rate,data]

def WriteAudio(filename,rate,matrix):
    """
    Escribe un archivo de audio .wav a partir de una matriz numpy con los datos
    del audio en mono o estéreo y la tasa de muestras por segundo.

    Parámetros
    ----------
    filename: string
        Nombre del archivo de salida .wav.
    matrix: numpy ndarray
        Matriz con audio en mono o estéreo.
    rate: int
        Tasa de muestras por minuto del audio.
    
    Retorna
    --------
    List: 
        [rate,data]
    rate: int 
        Muestras por segundo
    data: numpy ndarray 
        Matriz con audio en mono o estéreo
    """
    wavfile.write(filename,rate,matrix)

def Speed_Rep(input_filename,speed,output_filename):
    """
    Muestra en pantalla el reproductor de audio y guarda el audio con la
    velocidad dada por el usuario para el archivo .wav estipulado.

    Parámetros
    ----------
    input_filename: string
         Nombre o localización/path del archivo .wav de entrada.
    speed: float
        Velocidad con la que se va a reproducir el audio de destino.
    output_filename: string
         Nombre o localización/path del archivo .wav de salida
    
    Retorna
    ----------
    Reproductor en pantalla de iPython con el audio con la velocidad deseada.

    """
    rate,data=ReadAudio(input_filename)
    WriteAudio(output_filename,int(rate*speed),data)
    print(f"El archivo se guardo con éxito como {output_filename}")
    return playAudio(output_filename)

def Inverse_Rep(input_filename,output_filename):
    """
    Muestra en pantalla el reproductor de audio y guarda el audio reproducido
    desde atrás en el archivo .wav estipulado.

    Parámetros
    ----------
    input_filename: string
         Nombre o localización/path del archivo .wav de entrada.
    output_filename: string
         Nombre o localización/path del archivo .wav de salida
    """
    
    rate,data=ReadAudio(input_filename)
    #Convertimos a mono el audio original
    data=ConvertToMono(data)
    #Leemos la matriz desde atrás usando la notación de slicing de listas
    WriteAudio(output_filename,rate,data[::-1])
    print(f"El archivo se guardo con éxito como {output_filename}")
    return playAudio(output_filename)

def ConvertToMono(data):
    """
    Retorna un array de Numpy con la matriz de audio convertida a mono con el
    mismo dtype de Numpy que el original.

    Parámetros
    ----------
    data: numpy ndarray
        Matriz de Numpy que contiene audio en formato mono o estéreo.
    
    Retorna
    ----------
    mono: numpy ndarray
        Matriz de Numpy que contiene audio en mono.
    """
    #Se procede a leer el audio
    if len(data.shape)==1:
        canales=1
    else:  
        canales=data.shape[1]

    if canales==1:
        mono=data
    #En caso de que el audio sea de formato estéreo procede a su conversión
    elif canales==2:
        mono=[]
        stereo_dtype=data.dtype
        #Se obtienen los vectores correspondientes a cada canal de audio
        l=data[:,0]
        r=data[:,1]
        #Se suma cada canal de audio para obtener uno solo
        for i in range(len(data)):
            d=(l[i]/2)+(r[i]/2)
            mono.append(d)
        mono=np.array(mono,dtype=stereo_dtype)
    return mono


def Lowpass(data,alpha):
    """
    Filtro exponencial EMA de paso bajo que recibe una matriz con audio en
    mono y retorna una matriz con audio en mono del mismo tipo con el Filtro
    aplicado.

    Parámetros
    ----------
    data: Matriz Numpy ndarray
         Matriz con los datos de un audio mono.
    alpha: float
         Factor entre 0 y 1 que determina el suavizado y aplicación del filtro.
    """
    f0=alpha*data[0]
    filtered=[f0]
    for i in range (1,len(data)):
        #y[i] := α * x[i] + (1-α) * y[i-1]
        f=alpha*data[i]+(1-alpha)*filtered[i-1]
        filtered.append(f)
    return np.array(filtered,dtype=data.dtype)

def Highpass(data,alpha):
    """
    Filtro exponencial EMA de paso alto que recibe una matriz con audio en
    mono y retorna una matriz con audio en mono del mismo tipo con el Filtro
    aplicado.

    Parámetros
    ----------
    data: Matriz Numpy ndarray
         Matriz con los datos de un audio mono.
    alpha: float
         Factor entre 0 y 1 que determina el suavizado y aplicación del filtro.
    """
    f_Lowpass=Lowpass(data,alpha)
    filtered=[]
    for i in range(len(data)):
        f=data[i]-f_Lowpass[i]
        filtered.append(f)
    return np.array(filtered,dtype=data.dtype)

def Frequency_Cutoff(type,frequency,input_filename,output_filename):
    """
    Aplica el filtro exponencial EMA de acuerdo al tipo especificado por el
    usuario (Lowpass o Highpass).

    Parámetros
    ----------
    type: string (low or high)
        Tipo de filtro (Paso bajo o paso alto).
    frequency: float
        Frecuencia (Hz) de corte para aplicación de filtro.
    input_filename: string
         Nombre o localización/path del archivo .wav de entrada.
    output_filename: string
         Nombre o localización/path del archivo .wav de salida
    """
    rate,data=ReadAudio(input_filename)
    dt=1/rate
    alpha=(2*math.pi*dt*frequency)/((2*math.pi*dt*frequency)+1)

    print(f"α={alpha}")
    
    if type=="low":
        data_f=Lowpass(data,alpha)
    elif type=="high":
        data_f=Highpass(data,alpha)
    WriteAudio(output_filename,rate,data_f)
    print(f"El archivo se guardo con éxito como {output_filename}")

def Combinar_Audios(audio1,audio2,output_filename):
    """
    Muestra en pantalla el reproductor de audio y guarda el audio que combina 
    los dos audios de entrada.
    
    Parámetros
    ----------
    audio1: string
         Nombre o localización/path del archivo .wav de entrada.
    audio2: string
         Nombre o localización/path del archivo .wav de entrada.    
    output_filename: string
         Nombre o localización/path del archivo .wav de salida
    """    
    rate_1,data_1=ReadAudio(audio1)
    rate_2,data_2=ReadAudio(audio2)

    if len(data_1)>len(data_2):
        base_data=data_1.copy()
        insert_data=data_2.copy()
    else:
        base_data=data_2.copy()
        insert_data=data_1.copy()
        
    for i in range (0,int(len(insert_data))):
        base_data[i]=base_data[i]/2+insert_data[i]/2
        
    WriteAudio(output_filename,(rate_1+rate_2)//2,base_data)
    print(f"El archivo se guardo con éxito como {output_filename}")
    return playAudio(output_filename)

def FFT_Graphing(Graph_Title,data_1,rate_1,audio1_title,data_2,rate_2,audio2_title):
    """
    Grafica la transformada de fourier de dos audios, donde el eje x se
    muestra como la frecuencia en Hertz y el eje y la amplitud. Esto permite
    comparar de manera objetiva dos audios en su dominio frecuencia.

    Parámetros
    ----------
    Graph_Title: string
        Título de la gráfica.
    data_1: numpy ndarray
        Matriz con audio en mono.
    rate_1: int
        Muestras por segundo del audio.
    audio1_title: string
        Nombre a mostrar en la gráfica.
    data_2: numpy ndarray
        Matriz con audio en mono.
    rate_2: int
            Muestras por segundo del audio.
    audio2_title: string
        Nombre a mostrar en la gráfica.
    """
    plt.title(Graph_Title)
    plt.xlabel("Frecuencia (Hz)")
    plt.ylabel("Amplitud")

    fft_data_1=abs(fft(data_1))
    frecs_1=fftfreq(len(fft_data_1),(1/rate_1))
    x1=frecs_1[:(len(fft_data_1)//2)]
    y1=fft_data_1[:(len(fft_data_1)//2)]

    fft_data_2=abs(fft(data_2))
    frecs_2=fftfreq(len(fft_data_2),(1/rate_2))
    x2=frecs_2[:(len(fft_data_2)//2)]
    y2=fft_data_2[:(len(fft_data_2)//2)]

    plt.plot(x1,y1,color="r",label=audio1_title)
    plt.plot(x2,y2,color="g",label=audio2_title)
    plt.legend(loc='upper right', borderaxespad=0.)
    plt.show()
In [ ]:
!wget https://raw.githubusercontent.com/DarkNightSoldier/AudioProcessing/master/Happy.wav
!wget https://raw.githubusercontent.com/DarkNightSoldier/AudioProcessing/master/sweet.wav
!wget https://raw.githubusercontent.com/DarkNightSoldier/AudioProcessing/master/hfnoise.wav
--2020-07-04 21:28:34--  https://raw.githubusercontent.com/DarkNightSoldier/AudioProcessing/master/Happy.wav
Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 151.101.0.133, 151.101.64.133, 151.101.128.133, ...
Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|151.101.0.133|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 4423758 (4.2M) [audio/wav]
Saving to: ‘Happy.wav’

Happy.wav           100%[===================>]   4.22M  --.-KB/s    in 0.1s    

2020-07-04 21:28:35 (32.5 MB/s) - ‘Happy.wav’ saved [4423758/4423758]

--2020-07-04 21:28:36--  https://raw.githubusercontent.com/DarkNightSoldier/AudioProcessing/master/sweet.wav
Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 151.101.0.133, 151.101.64.133, 151.101.128.133, ...
Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|151.101.0.133|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 5427278 (5.2M) [application/octet-stream]
Saving to: ‘sweet.wav’

sweet.wav           100%[===================>]   5.18M  30.4MB/s    in 0.2s    

2020-07-04 21:28:36 (30.4 MB/s) - ‘sweet.wav’ saved [5427278/5427278]

--2020-07-04 21:28:37--  https://raw.githubusercontent.com/DarkNightSoldier/AudioProcessing/master/hfnoise.wav
Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 151.101.0.133, 151.101.64.133, 151.101.128.133, ...
Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|151.101.0.133|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 460044 (449K) [audio/wav]
Saving to: ‘hfnoise.wav’

hfnoise.wav         100%[===================>] 449.26K  --.-KB/s    in 0.03s   

2020-07-04 21:28:37 (12.9 MB/s) - ‘hfnoise.wav’ saved [460044/460044]

1. Reproducción lenta del audio

In [ ]:
Velocidad = 0.9 #@param {type:"slider", min:0.4, max:1, step:0.05}
Speed_Rep("Happy.wav",Velocidad,"slow.wav")
El archivo se guardo con éxito como slow.wav
Out[ ]:

2. Reproducción rapida del audio

In [ ]:
Velocidad = 1.65 #@param {type:"slider", min:1, max:5, step:0.05}
Speed_Rep("Happy.wav",Velocidad,"fast.wav")
El archivo se guardo con éxito como fast.wav
Out[ ]:

3. Reproducción hacia atrás del audio

In [ ]:
Inverse_Rep("Happy.wav","inverse.wav")
El archivo se guardo con éxito como inverse.wav
Out[ ]:

4. Limpieza del ruido de alta frecuencia

In [ ]:
fr=int(input("Especifique la frecuencia de corte en Hz  "))
Frequency_Cutoff("low",fr,"hfnoise.wav","limpieza.wav")
Especifique la frecuencia de corte en Hz  500
0.2819698001234662
El archivo se guardo con éxito como limpieza.wav
In [ ]:
rate_1,data_1=ReadAudio("hfnoise.wav")
data_1=ConvertToMono(data_1)

rate_2,data_2=ReadAudio("limpieza.wav")
data_2=ConvertToMono(data_2)

FFT_Graphing("FFT Con Ruido VS Lowpass fc",data_1,rate_1,"Audio sin filtrar",data_2,rate_2,f"Lowpass {fr} Hz")
In [ ]:
playAudio("hfnoise.wav")
Out[ ]:
In [ ]:
playAudio('limpieza.wav')
Out[ ]:

5. Ecualización de frecuencias altas y bajas

In [ ]:
alpha = 0.2 #@param {type:"slider", min:0, max:1, step:0.01}
rate,data=ReadAudio("Happy.wav")
data=ConvertToMono(data)

data_low=Lowpass(data,alpha)
data_high=Highpass(data,alpha)

FFT_Graphing(f"FFT Original vs Low pass α={alpha}",data,rate,"Original",data_low,rate,"Lowpass")
FFT_Graphing(f"FFT Original vs High pass α={alpha}",data,rate,"Original",data_high,rate,"Highpass")
In [ ]:
print(f"Lowpass α={alpha}")
WriteAudio("lowpass.wav",rate,data_low)
playAudio("lowpass.wav")
Lowpass α=0.2
Out[ ]:
In [ ]:
print(f"Highpass α={alpha}")
WriteAudio("highpass.wav",rate,data_high)
playAudio("highpass.wav")
Highpass α=0.2
Out[ ]:

6. Combinar dos audios

In [ ]:
Combinar_Audios("Happy.wav","sweet.wav","Combined.wav")
El archivo se guardo con éxito como Combined.wav
Out[ ]: